Skip to content

feat(cpp-boost-beast-server): add HTTP/1.1 server generator - #24783

Open
bold84 wants to merge 36 commits into
OpenAPITools:masterfrom
bold84:pr/cpp-boost-beast-server
Open

feat(cpp-boost-beast-server): add HTTP/1.1 server generator#24783
bold84 wants to merge 36 commits into
OpenAPITools:masterfrom
bold84:pr/cpp-boost-beast-server

Conversation

@bold84

@bold84 bold84 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

cpp-boost-beast-server: new OpenAPI HTTP/1.1 server generator (C++17)

Builds directly on current master, which includes the client OAS 3.1 work from #24760 (squash-merged as c27ee27572b). The diff contains only the server additions — 29 commits, 115 files (+21,612/−1,555): ~9,700 lines of generator source + tests, ~11,900 lines of committed sample/docs (the usual shape for a new-generator PR in this repo).

Adds a new cpp-boost-beast-server generator that produces a C++17 Boost.Beast HTTP/1.1 server from OpenAPI documents (OAS 3.0 and 3.1). It shares the model pipeline with the client generator (refactored into CppBoostBeastModelCodegen) and reuses the OAS 3.1 schema-validation runtime for request-body validation.

Scope

Generator

  • New cpp-boost-beast-server generator (beta) with typed request/responder contracts per operation (GetPetByIdRequest, GetPetByIdResponder), OAS parameter deserialization (path/query/header/cookie, incl. simple/label/matrix/form/spaceDelimited/pipeDelimited/deepObject-string-map styles), and a security seam (Authorizer, deny-by-default).
  • Shares the model pipeline with the client via CppBoostBeastModelCodegen; the shared decode contract is documented below (integers above 2^53 now decode from the exact wire lexeme on both generators).
  • OAS 3.1 request bodies are validated with the same generated schema-evaluator runtime the client uses; OAS 3.0 continues through the normal path.
  • Hard-rejects at generation time only what cannot route deterministically:
    • ambiguous path templates (/a/{x}/c vs /a/{y}/c), including query-suffixed duplicates: Router::splitPath strips the query at registration, so /responses?beta=true IS the route /responses and is reported as the literal same-shape duplicate it is (the OpenAI document's beta-path idiom);
    • equal-ranking collisions whose intersection is proved by a product-NFA witness search (level-capped trie BFS: finishing all levels is a disjointness proof, so branchy pairs like /v/{p1}a{p2}a{p3}a vs /v/a{p4}a{p5}a{p6} — which a step-budgeted DFS under-reports — can never ship as runtime-shadowed routes);
    • ranged status codes (2XX). Fails closed with precise diagnostics naming a verified witness path.
  • Degrades with a warning everything else the runtime cannot serve faithfully — chosen so real-world corpora (canonical petstore, the OpenAI spec) still generate compiling code, per the repository harness contract:
    • request media types are filtered to JSON (a mixed body accepts JSON and answers 415 to the rest; a JSON-less body loses its typed body);
    • response media types serialize as JSON;
    • security scheme types with no runtime credential extractor (oauth2/openIdConnect/mutualTLS) deny all requests with 401 rather than being silently unauthenticated;
    • parameters the codecs cannot decode from a raw string are dropped from the handler (content-style, form fields, non-deepObject object queries, non-scalar array items, cookie containers, unsupported styles, heterogeneous enums, enum-class dataTypes);
    • request bodies whose model resolves to std::variant (composition unions, including models aliased to one) get no typed body, since decode-side union matching needs the schema matcher the request path does not run.
  • Recovery: a multipart+JSON body whose JSON member $refs a generated model types the handler body exactly (its #include is appended via toModelImport); a body model colliding with the generated request struct is namespace-qualified.
  • RFC 9457 application/problem+json errors throughout, with WWW-Authenticate challenges for http-scheme security.

Runtime

  • Boost 1.81+ floor; Boost.URL as a compiled library; strand-based HttpServer with keep-alive, request version/keep-alive mirroring, 413 body-limit handling that closes the connection, and atomic single-completion guard on responders.
  • Router ranks literal path segments over parameters, so /pets/bulk wins over an earlier-registered /pets/{petId}.
  • Exact integer decode contract (shared with the client): a JSON double is a trustworthy integer image only while |value| ≤ 2^53; past that window the handler's ExactInstanceScope recovers the mathematical integer from the wire lexeme (9007199254740993.0 decodes exactly — the binary image silently rounds it), and with no lexeme available the decode refuses (400) instead of corrupting. value_to<std::int64_t> (whose cast-equality check agrees with the rounded image) is replaced by a JsonValueConverter<std::int64_t> specialisation on both generators.
  • UTF-8 length checks (maxLength/minLength, parameter and body surfaces) validate sequence structure, not just lead bytes: overlong encodings, surrogates, and out-of-range tails degrade to a strict byte count, so continuation bytes cannot be smuggled through a naive count.
  • Body pattern matching uses std::wregex; the validator stores UTF-16 surrogate pairs on 16-bit wchar_t (Windows) instead of silently truncating scalars above U+FFFF. README documents the platform split (on Windows . matches one UTF-16 code unit).
  • Expect handling tokenises every repeated field line per RFC 9110 §10.1.1 (unsupported token on line two answers 417, not ignored).
  • Parameter float/double gates compare against the destination range, so 1e400 answers 400 identically on platforms where long double is wider than double (x86 80-bit) instead of handing the service an infinity.
  • Deferred handler completions are supported: the response-write deadline is re-armed per response, so a worker-thread reply arriving after the read timeout still reaches the client.
  • Anonymous security alternatives (security: [] or an empty OR-group) bypass the authorization gate with no authorizer required.
  • Generated C++ builds clean under -Wall.

Sample, docs, CI

  • samples/server/petstore/cpp-boost-beast-server (byte-deterministic regeneration).
  • docs/generators/cpp-boost-beast-server.md and generator index entry.
  • .github/workflows/samples-cpp-boost-beast-server.yaml: Ubuntu / macOS / Windows build matrix with per-OS Boost installs (Windows: vcpkg boost-asio/boost-beast/boost-json/boost-url/boost-multiprecision).

Verification

  • Real-world corpus: the OpenAI OpenAPI document (498c71d, ~1.3 MB, 1,608 model files, 23 API files) generates, compiles with -Wall -Wextra at 0 errors / 0 warnings, and links — exercising the degrade policy (form fields, variant-alias and mixed bodies, enum-class params) and the recovery path (recovered JSON-member bodies with appended includes) at scale. On the current spec head the generator hard-rejects, correctly: GET /videos/characters/content is matched by both /videos/characters/{character_id} and /videos/{video_id}/content with equal ranking, and the beta paths (/responses?beta=true) register as literal duplicates of /responses — neither routes deterministically, and the diagnostics name both classes precisely.
  • Repository contract: AllGeneratorsTest passes (855 cases), i.e. this generator accepts src/test/resources/3_0/petstore.yaml (the spec that mixes XML/form/multipart payloads and an oauth2 scheme) exactly as the harness requires — degrade, not reject.
  • Focused Boost.Beast selection on this head: 290 tests across the cppboostbeast (250) + cppboostbeastserver (40) packages, 0 failures, 0 skips — the native generated-C++ runtime suites executed for real here (Boost headers present); they skip on runners without them. Includes the loopback runtime driver's new legs: exact-lexeme int64 above 2^53, repeated-Expect 417, CJK/overlong UTF-8 header counts, 1e400 double 400, and the branchy route-collision witness.
  • Petstore sample builds warning-free (server CMake build verified) and smoke-tested (501 stubs, 400 bad path param, 404 unknown route).
  • Sample regeneration is byte-deterministic (verified twice on this head). The shared-pipeline refactor changes the client sample only by the two deliberate shared-runtime contracts above: the int64 lexeme decode (JsonValueConverter<std::int64_t> specialisation in the generated models + ValidationTypes.h) and the stricter UTF-8 code-point count in Oas31Validator.h; server and client samples are regenerated together.

Notes

  • Kept as draft pending maintainer bandwidth; the feat(cpp-boost-beast): add OAS 3.1 schema validation #24760 dependency is resolved (merged), so the diff is final and review-ready.
  • The generator is marked beta; the runtime is designed for embedding (all generated sources in the project), not yet published as a standalone library.

bold84 added 7 commits August 27, 2026 01:42
…rver

Move the direction-agnostic document pipeline, model lowering, parameter
serialization facts, dialect policy, and schema-IR emission from
CppBoostBeastClientCodegen into CppBoostBeastModelCodegen; extract
CppBoostBeastOperationFacts from the client template assembler; add
additionalEmbeddedTemplateDirs locator support and move shared
model/validation templates to cpp-boost-beast-common. Client generated
output is byte-identical; cppboostbeast suite (158 tests) green.
Add the cpp-boost-beast-server generator (BETA) emitting a C++17
Boost.Beast HTTP/1.1 server: strand-per-connection sessions with
message_generator responses, encoded-segment routing with 404/405+Allow,
OAS parameter deserialization (path simple/label/matrix, query
form/space/pipe/deepObject, header simple, cookie form) with enum,
pattern, and bound validation into RFC 9457 problem responses, JSON body
codec over generated model to/fromJsonValue APIs, OR-of-AND security
extraction with a deny-by-default Authorizer seam, single-shot strand
posting responders, and an addApiImplStubs quick-start main. CMake links
Boost 1.81+ json+url compiled libraries; -Wall/-W4 clean.
…e suites

Add the server-regression OAS 3.1 fixture (path/query/header/cookie
styles, enum/pattern/bound constraints, bearer + apiKey security),
12-test codegen suite (defaults, contract emission, stubs, IR stripping,
multipart/x-www-form-urlencoded/text-*/event-stream/content-style/
cookie-matrix/ambiguous-route rejections, 3.0 compatibility), and the
native loopback runtime test compiling and running the generated server
against real sockets: 200/201/204 happy paths, 400 problem+json with
errors[] for every constraint class, 404, 405+Allow, 413, 415, 401 with
and without credentials, keep-alive, label-pattern paths, and
pipe-delimited collections. Header params now look up lowercased field
names; enum allow-lists render unescaped.
Add the deterministic cpp-boost-beast-server petstore sample (builds
clean with -Wall and serves 501 stubs / 400 / 404 over HTTP/1.1), the
generated generator documentation, and a three-OS sample build workflow
covering Boost json+url consumers. API headers now emit the model
namespace using-directive only when an API actually references a
generated model class (map-only APIs such as store inventory compile
standalone).
…erage

Runtime: anonymous security alternatives (security: []) now bypass the
authorization gate; 413 body-limit responses close the connection instead
of re-reading leftover body bytes as a new request; responses mirror the
request HTTP version and keep-alive preference; HttpServer::stop posts
the acceptor close onto its strand; ResponderCore's completion flag is
atomic and the handler-exception 500 routes through the guard; 401s
carry WWW-Authenticate for http-scheme challenges; cookie names strip
leading OWS; BodyJson handles uint64 numbers above INT64_MAX without
throwing past the invalid_argument catch and supports any-type bodies;
parseScalar enforces whole-input match and rejects inf/nan; problem JSON
escapes bytes >= 0x7f to keep bodies valid UTF-8.

Generator: request-body and parameter $refs are resolved before fact
extraction so bodies and constraints survive; declared media-type
parameters are normalized out of kMediaTypes; ranged status codes
(2XX) and oauth2/openIdConnect/mutualTLS schemes are rejected at
generation; integer/number/bool enums are validated at runtime like
string enums; header and cookie params gain enum checks; route shape
keys mirror the runtime splitter; dead x-server-route/kind/inner facts
and the bodyKind helper are removed; shared OperationFacts gains the
Apache header.

Loopback coverage: anonymous-op bypass against a denying authorizer,
integer-enum rejection/acceptance, and conventional spaced Cookie
headers.
…tests

Generation gate:
- reject array/object cookie params, non-scalar array items, object
  params other than query deepObject string maps, heterogeneous enums
- fix malformed security-scheme diagnostic quoting

Runtime:
- splitMatrixExploded(): strip repeated name= per element for
  style=matrix explode=true path parameters
- router literal-over-parameter ranking (most literal segments wins,
  ties keep registration order) so /pets/bulk beats /pets/{petId}
- re-arm the stream timer in send_response so deferred handler
  completions are not aborted by an expired read deadline
- mirror request version/keep-alive on the 413 body_limit path
- deepObject required-missing now reports 400
- shared param-constraints partial: uniform pattern/length/bound
  ladders across query, header, and cookie scalar parameters
- jsonEscape passes UTF-8 through; escapes only C0 controls and DEL

Rendering: minimum/maximum bounds emit long-double-safe literals
(plain integers gain .0), so int64 extremes compile under -Wall.

Tests: four new gate rejection tests plus a deepObject acceptance
test; runtime test hardened (compiler/Boost skip guards, file-based
output redirection, process-tree termination on timeout) and a new
validation-disabled end-to-end case; loopback driver covers matrix
explode, spaceDelimited, deepObject required, literal ranking,
deferred completions, WWW-Authenticate challenges, HTTP/1.0
mirroring incl. 413, cookie decoding, and duplicate-completion
guarding; locator precedence test now probes a template present in
both embedded dirs.

CI: Windows sample job installs boost-asio/beast/multiprecision
ports (the build failed on missing beast and multiprecision headers).

Docs: README ServerOptions table for readTimeoutSeconds/bodyLimitBytes;
sample regenerated deterministically.
@bold84
bold84 force-pushed the pr/cpp-boost-beast-server branch from f8d7a8b to 99775fa Compare August 26, 2026 18:45
bold84 added 11 commits August 27, 2026 02:23
…rity schemes

AllGeneratorsTest requires every registered generator to generate from
the canonical 3_0/petstore.yaml, which declares XML/form/multipart
payloads and an oauth2 scheme; the previous hard-reject gate broke that
contract for the entire project on every CI leg.

- preprocessOpenAPI now logs precise warnings for non-JSON request
  media types, non-JSON response media types, and security scheme types
  with no runtime credential extractor, while still throwing for the
  compile-breaking/route-ambiguous categories (parameter styles and
  shapes, cookie containers, non-scalar array items, non-deepObject
  object params, heterogeneous enums, ambiguous routes, ranged codes)
- the assembler filters declared request media types down to JSON:
  mixed bodies accept JSON and answer 415 to the rest, JSON-less bodies
  drop the typed body field entirely (hasBody=false, compiles clean)
- unsupported scheme types remain in the route table as declared; the
  runtime's structurallySatisfied() denies those requests with 401
  instead of the generation failing
- five rejection tests rewritten as degrade tests asserting the exact
  generated contracts, plus a canonical-petstore regression test
- verified: AllGeneratorsTest 855/855, beast suites 183 green, both
  samples regenerate with zero diff
The feature set advertised FormUnencoded/FormMultipart support while
the server never parses those payloads (they degrade to no typed body).
Exclude both parameter features and regenerate the generator docs page.
… variant bodies

Move the 16 chunked OAS 3.1 schema-IR templates from the client-only
embedded dir into cpp-boost-beast-common so the server generator (whose
own dir never carried them) can reach the chunked IR path for large
specs, and pin resolution from both generators with a locator test.

Wipe the inherited DefaultCodegen typeMapping before seeding it, same
as the client generator: AnyType -> oas_any_type_not_mapped is a
placeholder header this family never provides, and the OpenAI corpus
(FunctionToolParam output_schema anyOf) reaches it through a freeform
branch that must resolve to boost::json::value.

Render std::variant, std::optional, and std::monostate bodies in
BodyJson.h: composition-typed responses serialize the active branch via
std::visit, and null branches serialize as JSON null instead of failing
to compile.
…d-body JSON members

Parameter shapes the JSON runtime cannot decode (content-style, form
fields, object/array queries outside deepObject/scalar rules, cookie
containers, heterogeneous enums, enum-class dataTypes) now degrade to a
dropped handler field with a warning instead of rejecting the document,
mirroring the media-type policy so real-world corpora generate code.
The OpenAI spec drove the rules: the classifier keys off the resolved
dataType the templates emit, so parseScalar can never see a type without
an overload.

Request-body recovery: a multipart+JSON body whose JSON member $refs a
generated model now types the handler body exactly (the model's include
is appended via toModelImport when DefaultCodegen did not import it);
models aliased to std::variant degrade to no typed body because
fromJsonLeaf cannot decode unions; a body model colliding with the
generated request struct is namespace-qualified. The model
using-directive flag accounts for recovered bodies. The gate now
rejects only non-deterministic routing (ambiguous templates, ranged
codes). Tests: six degrade rewrites plus four new contracts.
…e the right logger

BodyJson.h declares a std::optional overload but relied on <variant>
transitively providing <optional>; include it directly. The shared model
codegen hardcoded its logger to CppBoostBeastClientCodegen, so server
degrade warnings were attributed to the client class; use getClass().
Regenerated petstore server sample picks up the new include.
… rule

ArchUnitRulesTest requires slf4j Logger fields to be non-public,
non-static and final (PR OpenAPITools#8799); the new assembler logger was static.
Both call sites are instance methods, so the field becomes an instance
logger.
…tests

- Router: tokenize embedded path expressions in one segment; shape keys
  keep literal text distinct from placeholders
- RequestContext held via shared_ptr through the handler/service chain
- Optional request bodies no longer fail presence checks when absent
- Multi-tag operations get per-operation contract type names
- ParamCodecs: float narrowing range checks, exclusive numeric bounds,
  collection/item constraints (new param-container-constraints include),
  strict label/matrix percent-encoded codecs, exact integer comparisons
- Problem JSON: sanitize malformed UTF-8 in error details
- Gate: wildcard response media no longer accepted as JSON; request-body
  schema selected per declared media; form-encoding hard-reject moved to
  a server-side override so the shared model path no longer rejects the
  client corpus; MultiServer feature claim set to false
- Generated CMake: warnings are errors by default (opt-out flag added)
- README quick-start renders real operations and attach sequence
- Runtime regression spec/driver extended to cover all of the above
… regression

The sample workflow ran the runtime test with -pl modules/openapi-generator
alone, so the 7.26.0-SNAPSHOT sibling (openapi-generator-core) could not be
resolved from the snapshot repo on a clean runner checkout. Build the
upstream modules with -am and tolerate modules without the selected test.
…l-closed codecs, review gaps

- BodyJson: unwrap tagged CompositionBranchValue branches so oneOf
  responses whose C++ types collide serialize through std::visit
- ParamCodecs: reject hex floats explicitly, accept ERANGE underflow,
  drop the dead errno store; document the whole-input match
- param-constraints / param-container-constraints: guard std::regex
  construction so an out-of-subset pattern fails closed with 400
  instead of retry-throwing 500 per request (absent/empty skip it)
- Assembler: apiNamespace is constructor-injected (the operations-map
  merge runs after postProcessOperationsWithModels, which made the
  collision guard inert); mixed bodies type the JSON member model even
  when an unparseable member came first; README facts carry
  model-namespace-qualified send types
- Codegen: exclude Host/BasePath global features (never mounted); the
  server overrides the shared form-encoding reject because it degrades
  flattened form fields with a warning instead of aborting
- Workflow: trigger the sample CI on generator, template, and test
  resource changes; add generation/runtime coverage for the new paths
Deterministic re-run of bin/generate-samples.sh and
bin/utils/export_generator.sh after the fix commit: README quick-start
qualifies model response types, BodyJson.h gains the tagged-branch
serializer, ParamCodecs.h gains the hex-float gate, and the feature
table now reports Host/BasePath as unsupported.
@bold84
bold84 marked this pull request as ready for review August 27, 2026 18:37

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 104 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread samples/server/petstore/cpp-boost-beast-server/model/Pet.cpp
Comment thread samples/server/petstore/cpp-boost-beast-server/api/PetsApi.cpp Outdated
Comment thread samples/server/petstore/cpp-boost-beast-server/server/BodyJson.h Outdated
Comment thread samples/server/petstore/cpp-boost-beast-server/server/BodyJson.h
bold84 added 3 commits August 28, 2026 07:51
…mplates

- route-ambiguity witness probe + adjacent-expression rejection in the
  server support-surface gate
- pre-escaped literals (path, operationId, baseName, security fields)
  rendered with triple-brace interpolation only
- license header sanitizes '*/' and NUL from info fields
- required-null rejection, decode-time reset of every member,
  nullptr leaf assignment, integral-double integer bodies
- two-stage read with interim 100 Continue, 413 version mirroring,
  404 query stripping, factory-only HttpServer with null-router guard
- JSON numeric wire grammar + locale-independent codecs, cookie
  quoted-string stripping, UTF-8 code-point lengths, unanchored
  regex_search with fail-closed grammar gate (Unicode property
  escapes refused explicitly, never approximated)
- enum-always-invalid and present-guarded container constraints,
  path-item parameter lookup, nullable-body std::optional support,
  variant key normalization, schema-IR body validation before decode
- CI triggers cover the shared model pipeline and common templates
- server runtime driver: unicode property-escape fail-closed legs,
  100-continue, oversized-body version mirror, 404 query stripping,
  PORT/HttpServer ownership probes already covered
- exact-number driver: validator-level property-escape refusal incl.
  doubled-backslash literal case
- preserve-additional-properties driver owns its serialized value
  (the old code bound a reference into a destroyed temporary)
- CI triggers widened to shared model-pipeline sources
Regenerated after the review-round template fixes: fail-closed
validator (property escapes), decode resets, required-null rejection,
HTTP two-stage read + interim 100, codecs with JSON wire grammar,
sample README policy notes.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 45 files (changes from recent commits).

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

Re-trigger cubic

Comment thread samples/server/petstore/cpp-boost-beast-server/server/HttpServer.cpp Outdated
Comment thread samples/server/petstore/cpp-boost-beast-server/server/BodyJson.h Outdated
Comment thread samples/server/petstore/cpp-boost-beast-server/server/ParamCodecs.h Outdated
Comment thread samples/server/petstore/cpp-boost-beast-server/README.md
Comment thread samples/server/petstore/cpp-boost-beast-server/server/Problem.h Outdated
bold84 added 3 commits August 28, 2026 08:10
The cross-layout overlap probe's glob merge consumed the other side's
literals without emitting them (and never emitted while one side's
wildcard ran to the end), so /a/{x}b vs /a/a{y} produced no candidate
witness and the equal-ranking collision slipped the gate. Every
transition now either advances strictly or appends the absorbed byte;
a step budget bounds the search, keeping the probe proof-only (it can
under-report, never falsely reject). Regression-pinned by
rejectsCrossLayoutRouteOverlapWithWitness.
- witness overlap + adjacent-expression codegen rejections
- license-header terminator sanitization
- nullable model body kept typed as std::optional
- HTML-significant route names rendered unescaped
- runtime: body enum/required-null 400 legs (validation-leg gated),
  404 query-strip, stepwise Expect: 100-continue interim proof,
  path-item inherited parameter constraint legs
http::message::contains() only exists since Boost 1.85; the Linux CI
image ships an older Beast and the sample build failed on it. count()
has been available since the first Beast release.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 7 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

- parse Expect as a token list: interim 100 only when every expectation is
  supported, 417 (RFC 9110 10.1.1) otherwise, so no client waits out the read
  timeout for an interim response that never comes
- rewrite parseFloatScalar with a bounded significand and stepped exponent
  scaling: 0e400 stays zero (no 0 * inf), 1e400 saturates to infinity and
  dies at the finiteness gate, long finite decimals no longer overflow the
  accumulator; underflow still rounds toward zero
- count UTF-8 code points by validating whole sequences: a malformed byte is
  never skipped as a continuation, so percent-decoded garbage cannot slip past
  maxLength
- reject integral-double int64 leaves outside 2^53 instead of trusting a
  possibly-rounded double; keep <limits>/<optional> as direct includes
- fail closed for bool/number parameters whose enum has zero representable
  members; reset inherited explicit defaults through ModelPropertyStorage.value
- accept any recursively decodable inner type for an optional request body
- document the parameter-vs-body pattern grammar split and the rationale for
  the whole-payload numeric representability gate; exercise Pet.note, Expect
  tokens, and the UTF-8 policy in the loopback regression

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 14 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

bold84 added 3 commits August 28, 2026 11:59
…exeme

tryGetMathematicalInteger trusted any integral double inside the
destination range, but above 2^53 the double is the image of a 2-or-
wider rounding band and every cast-based exactness check agrees with
the band's centre: 9007199254740993.0 arrived as ...92 through
value_to<int64_t>, through the model pipeline, and through the
server's int64 body leaf alike.

- Oas31ExactJson.h gains exactNumericLexeme + exactLexemeToInteger:
  inside a live ExactInstanceScope the decimal text is authoritative
  and converts exactly, range-checked against the destination.
- tryGetMathematicalInteger keeps integral doubles only while
  |value| <= 2^53; past that window it recovers from the lexeme or
  refuses (fail closed, 400, never a corrupted int64).
- JsonValueConverter<std::int64_t> specialisation replaces boost's
  value_to path, which never consulted the lexeme.
- BodyJson.h's int64 leaf now delegates to the shared helper, one
  judgment for model, body, and validator paths.
- Client and server samples regenerated; exact-runtime boundary
  expectations updated to the new (stricter) contract with paired
  no-lexeme-refusal / in-scope-exact legs.
…ed trie

The ambiguous-route gate enumerated candidate witnesses with a
step-budgeted depth-first glob merge. Branchy-but-intersecting pairs
(alternating wildcard/literal layouts like /v/{p}a{p}a{p}a vs
/v/a{p}a{p}a{p}) exhaust the step cap without emitting a witness, so a
real collision could be silently under-reported and shipped as a
runtime-shadowed route.

Replace it with product-NFA trie BFS over (i,j) token positions:
epsilon-closure per level, emitted chars advance at least one side, so
a shortest witness has length at most n+m. Finishing all levels is a
disjointness proof, not a timeout. Regression test drives the old
under-report through to a rejection naming the witness path.
…ct lists, double gates

Four wire-level correctness gaps from the third review round, each with
a loopback driver leg:

- UTF-8 length checks (maxLength/minLength) now validate sequence
  structure, not just lead bytes: overlong encodings, surrogates, and
  out-of-range tails degrade to a strict byte count and cannot be
  smuggled through as one code point (param codecs and the body
  validator share the grammar).
- The body validator's std::wregex path encodes scalars above U+FFFF as
  UTF-16 surrogate pairs on 16-bit wchar_t (Windows); storing the bare
  code point truncated it silently. README documents the platform
  split.
- Expect handling tokenises EVERY repeated Expect field line per
  RFC 9110 5.3, not just the first: an unsupported token on line two
  answers 417 instead of being ignored.
- parseFloatScalar gates against the DESTINATION range for float and
  double alike. Where long double is wider than double (x86 80-bit),
  1e400 parses finite and the old double-only gate handed the service
  an infinity cast out of the schema's range; both platforms now
  answer 400 identically.

Samples regenerated.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 33 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread samples/server/petstore/cpp-boost-beast-server/model/Error.cpp
Router::splitPath strips the query string at registration, so
'/responses?beta=true' registers the very route '/responses' — a
literal duplicate the shape gate should name as such. The key was
computed over the raw template, letting every beta-path pair slip to
the witness probe and get mislabelled as registration-order ambiguity
with a fabricated witness. The OpenAI document's
'/x?beta=true' idiom now produces the honest
'have the same shape' diagnostic; regression test added.
bold84 added 3 commits August 28, 2026 14:24
…e image

- Inside an ExactInstanceScope the original token outranks its binary
  image unconditionally: an image check accepts 1.0000000000000001
  (rounds to 1.0) and refuses 9223372036854775807.0 (rounds past
  int64's max), inverting both verdicts.
- The 2^53 trust window is open on both signed edges: -2^53 is also the
  ties-to-even image of -9007199254740993, so the negative boundary
  image was decoded as a different wire integer than the positive one
  already was. Zero stays accepted for unsigned destinations (no token
  shares its image).
- Regenerated both committed samples deterministically.
Two suppression rules in the product-trie witness search could
"prove" two routable templates disjoint when they genuinely
collide:

- the level-0 accept (both segments matching an empty path
  segment) was filtered out, so pairs colliding only through
  empty captures went unreported;
- every step where BOTH sides merely absorb a char was skipped,
  but a whole-segment capture may not be empty (Router::matches
  refuses it) and absorbs only at index 0, so '/{p}' vs '{q}'
  needed exactly one root both-absorb char to yield a witness.

Allow exactly one both-absorb step from the root state at level 0
(complete: whole-segment captures only need one stretched char,
partial captures may be empty), keep empty candidates in the list,
and let the caller's whole-path Router::matches mirror reject
unverifiable ones. New regression: '/{p}/a/b' vs '/{q}/a{r}/b'
 collide on /c/a/b and must be refused; the old search generated
them.
The repeated-100-continue probe broke out of its read loop as soon
as the 200 status line appeared in the transcript. The body can
split across socket segments (headers and partial body in one read,
the tail in the next), so the 'twice' assertion raced the body and
the probe was flaky under load. Stop only once both the status line
AND the body marker arrived; EOF (Connection: close) still ends the
read as before.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

bold84 added 2 commits August 28, 2026 15:36
…budget

The both-absorb root step is only reachable when both flattened segments
can absorb a char at index 0 (a wildcard each). Allocate the extra level
only then, and pin the 4M cell guard to the pre-root-absorb product so a
pair that was searchable before the level existed (e.g. two 125-char
literal-initial segments: 126x126x251 = 3,984,876 base vs 4,000,752 with
the level) stays searchable instead of under-reporting its collision.
Regression test proves the near-budget collision is still reported.
Inside the doubles' exact window every image names exactly one integer,
so a signed destination whose full range fits within it (int32 and
narrower) must accept its own minimum -2^digits; the open lower edge is
only sound for destinations reaching the precision boundary (int64),
whose -2^53 image is ambiguous with -9007199254740993. Keep the upper
edge open everywhere. Native regression covers int32 min/max acceptance
and the int64 -2^53 refusal.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

bold84 added 2 commits August 28, 2026 16:06
Each template forces at least 124 literal 'l' chars into the colliding
segment (captures may be empty), so 124 is the shortest joint match.
Asserting the full 'both match' diagnostic rules out passing on a
shorter prefix inside a wrong-length witness.
The previous wording implied -2^53 itself is accepted for a narrower
signed destination; the destination-bounds check rejects it as
out-of-range. What is accepted is the destination's own minimum, whose
image is unambiguous inside the exact window. Regenerated both samples.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants